Network Extension

RSS for tag

Customize and extend the core networking features of iOS, iPad OS, and macOS using Network Extension.

Posts under Network Extension tag

201 Posts

Post

Replies

Boosts

Views

Activity

Network Extension Resources
General: Forums subtopic: App & System Services > Networking DevForums tag: Network Extension Network Extension framework documentation Routing your VPN network traffic article Filtering Network Traffic sample code TN3120 Expected use cases for Network Extension packet tunnel providers technote TN3134 Network Extension provider deployment technote TN3165 Packet Filter is not API technote Network Extension and VPN Glossary forums post Debugging a Network Extension Provider forums post Exporting a Developer ID Network Extension forums post Network Extension Framework Entitlements forums post Network Extension vs ad hoc techniques on macOS forums post Network Extension Provider Packaging forums post NWEndpoint History and Advice forums post Extra-ordinary Networking forums post URL filter: WWDC 2025 Session 234 Filter and tunnel network traffic with NetworkExtension URL filters documentation Filtering traffic by URL sample code Setting up a PIR server for URL filtering sample code Using the Bloom filter tool to configure a URL filter sample code PIR Service Example open source server sample and specifically its documentation Wi-Fi management: Understanding NEHotspotConfigurationErrorInternal forums post See also Networking Resources for general networking resources, including information about Wi-Fi. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
0
0
4.0k
Aug ’26
Best practices for parallelizing handleNewFlow(_:) or async verdicts in NETransparentProxyProvider
Hello, I am working on network traffic analyser for DLP. The solution is based on NETransparentProxyProvider due to necessity data modifying. To my knowledge, the Transparent Proxy API does not have a concept of an "asynchronous/delayed verdict" for flow authorization. The handleNewFlow(_:) method appears to be invoked sequentially for each new network flow that matches our interception rules. The challenge we are facing is that a portion of our proxy rules and configurations resides in a LaunchDaemon, which the Network Extension communicates with via XPC. Such separation is required by our cross-platform code base. Thus, we are forced to make a synchronous XPC call within handleNewFlow(_:) and block the thread while waiting for the interception verdict from the daemon. This creates a significant bottleneck when establishing new network flows. The current implementation works without issues, but we are always looking for ways to improve performance. Are there any best practices or recommended approaches for parallelizing handleNewFlow(_:)? Thank you in advance.
1
0
52
18h
IKEv2 Personal VPN: Child SA torn down after 120s idle (NEIKEv2ErrorDomain Code=15) with DisconnectOnIdle already NO
We ship a consumer VPN app on iOS, iPadOS and tvOS using a Personal VPN configuration: NEVPNManager with NEVPNProtocolIKEv2, EAP-MSCHAPv2, no MDM profile installed. After exactly 120 seconds with no traffic, iOS destroys the Child SA and disconnects the tunnel. I would like to know whether that timer is configurable, and if not, what the intended mitigation is. From a device sysdiagnose (iPhone, iOS 26.6, build 23G71): NEIPSecDBStatsUpdate: SA is idle for past 120 secs KernelSASession[1, IKEv2 Session Database] idle timeout SA Internal SAID = 2 SPI = C53320D1 Direction = Outbound ChildSA[1] state Connected -> Disconnected error Domain=NEIKEv2ErrorDomain Code=15 "IdleTimeout" <NEIKEv2Provider: Primary Tunnel>: stopping tunnel since Child disconnected nesessionmanager: plugin disconnected with reason "Tunnel was idle for too long" This happened 12 times across a 9.2 hour overnight capture on one idle device. Median time before the tunnel re-established was 14m35s. DISCONNECT-ON-IDLE IS NOT ENABLED The same sysdiagnose shows the plugin's own configuration as: disconnectOnIdle = NO disconnectOnIdleTimeout = 0 These are the stock defaults. There is no disconnectOnIdle property on the public NEVPNProtocol, so a Personal VPN app cannot set them either way. The installed SA parameters contain no idle field at all, only "Lifetime Seconds = 1800", which is honoured correctly: the same capture shows 20 clean rekey cycles. WHAT I HAVE RULED OUT iOS logs a distinct stop reason for each of the following, and none of them occurred across 24 teardowns: "On Demand Disconnect rule matched", "Tunnel was terminated by the server", "Server is not responding", "Network changed, tunnel no longer viable", "Device went to sleep", "Stop command received". The only reason recorded was "Tunnel was idle for too long". On the gateway (strongSwan), IKE rekey and reauth are disabled, uniqueids is never, and DPD is answered in roughly 200ms right up to the teardown. The gateway considers the tunnel healthy at the moment iOS tears it down. DPD does not reset the timer, which makes sense: DPD is an INFORMATIONAL exchange on the IKE SA, whereas the log shows the timer measuring the OUTBOUND Child SA (SAID 2). THE APP CANNOT SEE THIS HAPPEN NEVPNConnection.fetchLastDisconnectError() returns nil for this teardown, because the error is in NEIKEv2ErrorDomain rather than NEVPNConnectionErrorDomain. The app has no supported way to detect that the tunnel dropped for this reason, or to distinguish it from a user-initiated disconnect. It is only visible in a sysdiagnose. TRAFFIC IS NOT HELD DURING THE RECONNECT With Connect On Demand enabled (NEOnDemandRuleConnect, interfaceTypeMatch .any), traffic after the teardown does not wait for the tunnel. On device wake following one of these drops: 01:32:43 device wakes 01:32:43 [C331 ... :443] path:satisfied @0.001s, interface: en0[802.11], uses wifi 01:32:44 [C331 ... :443] flow:finish_connect @0.623s (over en0) 01:32:47 tunnel status changed to connected so flows complete over the physical interface for several seconds before the VPN re-establishes. On-demand triggered the reconnect but did not delay the traffic. For a VPN product this window is the part that concerns me most. QUESTIONS Is the 120 second Child SA idle timeout configurable for a Personal VPN using NEVPNProtocolIKEv2, from the app or from a configuration profile? If DisconnectOnIdle / DisconnectOnIdleTimer are meant to control it, why does the teardown occur when they are NO / 0? If it is not configurable, is application-generated keepalive traffic the intended workaround? If so, how is that expected to work while iOS has the app suspended, which is exactly when a tunnel goes idle? Would server originated traffic that elicits a client response be a supported approach? Is there any supported way for an app to be notified of this teardown, given that fetchLastDisconnectError() returns nil for it? Is the behaviour in "traffic is not held during the reconnect" expected for NEOnDemandRuleConnect, or should matching flows be delayed until the tunnel is up? Is includeAllNetworks the only supported way to close that window? Happy to supply the full sysdiagnose privately.
1
0
230
1d
Memory leak in CFNetwork (PACClient/PACQuery) when using NETransparentProxyProvider with Auto Proxy Discovery enabled
Hello, I have encountered unexpected behavior when running a Network Extension that implements NETransparentProxyProvider. This extension is part of a DLP (Data Loss Prevention) solution. If the "Auto proxy discovery" option is enabled for the Wi-Fi connection on the managed host, the leaks tool reports memory leaks with the following root cycles: ... 11 (1.03K) ROOT CYCLE: <CFRunLoopSource 0xa430cc540> [192] 10 (864 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACClient> 0xa430f4000> [224] CYCLE BACK TO <CFRunLoopSource 0xa430cc540> [192] 6 (400 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACQuery> 0xa43118080> [128] 2 (80 bytes) ROOT CYCLE: 0xa42ca9000 [32] 1 (48 bytes) ROOT CYCLE: <__NSMallocBlock__ 0xa42804de0> [48] CFNetwork invocation function for block in PAC::PACClient::initialize(void const*, __CFURL cons..." 1 (32 bytes) ROOT CYCLE: <std::__shared_ptr_pointer<BlockHolderVar<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>*, SmartBlockWithArgs<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>::Deleter> 0xa4343da80> [32] 2 (160 bytes) <NSURL 0xa42840310> [112] 1 (48 bytes) _clients --> <CFString 0xa42c08fc0> [48] 1 (160 bytes) <NWConcrete_nw_pac_resolver 0xa4280cb40> [160] 1 (48 bytes) <CFError 0xa42804ed0> [48] 1 (32 bytes) <std::__shared_ptr_pointer<__CFError*, Deleter_CFRelease> 0xa4343dc20> [32] ... 11 (1.03K) ROOT CYCLE: <CFRunLoopSource 0xa43128540> [192] 10 (864 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACClient> 0xa430f4a80> [224] CYCLE BACK TO <CFRunLoopSource 0xa43128540> [192] 6 (400 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACQuery> 0xa43118880> [128] 2 (80 bytes) ROOT CYCLE: 0xa428105e0 [32] 1 (48 bytes) ROOT CYCLE: <__NSMallocBlock__ 0xa428887b0> [48] CFNetwork invocation function for block in PAC::PACClient::initialize(void const*, __CFURL cons..." 1 (32 bytes) ROOT CYCLE: <std::__shared_ptr_pointer<BlockHolderVar<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>*, SmartBlockWithArgs<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>::Deleter> 0xa4343f5c0> [32] 2 (160 bytes) <NSURL 0xa428424c0> [112] 1 (48 bytes) _clients --> <CFString 0xa42c0a640> [48] 1 (160 bytes) <NWConcrete_nw_pac_resolver 0xa4280d7c0> [160] 1 (48 bytes) <CFError 0xa42888390> [48] 1 (32 bytes) <std::__shared_ptr_pointer<__CFError*, Deleter_CFRelease> 0xa4343f4c0> [32] ... The extension creates an nw_connection_t to the remote host for each handled flow like this: nw_parameters_t parameters = nw_parameters_create_secure_tcp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION); nw_endpoint_t connectTo = nw_endpoint_create_host([endpoint.hostname UTF8String], [endpoint.port UTF8String]); nw_connection_t connection = nw_connection_create(connectTo, parameters); When "Auto proxy discovery" is disabled, everything works as expected, and no memory leaks or issues are observed. Could you please advise on how to resolve or work around this issue? Thank you in advance!
2
0
319
4d
Potential Network Extension memory leak
I've been investigating memory consumption issues with a network extension. It seems as though the "leak" exists within the network extension framework. Looking at a memory graph, there appears to be a _socketFlows dictionary or similar in the network extension framework that holds all of the flows and they continue to pile up and consume memory. The problem seems to be particularly bad when I leave my MacBook plugged in and running over the weekend and come back on Monday. For instance, in that time period, the memory consumption grew from <100MB to >1GB. To try to narrow this down to see if it was an "us" problem or truly in the framework, I installed Objective-See Foundation's Lulu tool, which also uses a network extension. It similarly saw memory grow from around 30MB or less to 600MB in the same time frame. Has anyone else seen this? We do have an open feedback ticket FB18731867 that might be related that has to do with seeing multiple instances of the same data filter provider started and maybe running in a network extension.
5
0
679
5d
NEPacketTunnelProvider for local device wide bandwidth pacing without a remote VPN server
We are developing a consumer digital wellbeing app and are evaluating an optional feature that deliberately slows network traffic when explicitly enabled by the user. The proposed architecture is: NEPacketTunnelProvider device wide traffic no MDM or per-app VPN no remote VPN server packet processing/forwarding performed locally on device no application layer inspection no traffic profiling or advertising use no network data uploaded to our servers bounded latency/bandwidth shaping only user can disable it immediately The goal is user controlled “intentional bandwidth” rather than security, privacy filtering, circumvention, or remote VPN access. I’d like to clarify: Is NEPacketTunnelProvider an appropriate public API for this kind of local device wide bandwidth pacing? Is it supported to consume packets via packetFlow, process/forward them locally, and send them to the public Internet without a remote VPN endpoint? Is intentional bandwidth/latency shaping considered an acceptable use of the Network Extension entitlement? Would this still be treated as a “VPN service” for App Review purposes even though no traffic is routed through a remote VPN service? If so, would the organization-enrollment requirement for VPN apps apply? Is there another public API Apple recommends for this use case? I understand that the App Review questions may ultimately need to be answered by App Review rather than the forums. My main goal here is to validate whether this is a supported NetworkExtension architecture before investing in the packet processing implementation.
1
0
89
5d
Does "Connectivity Assist" bypass NEPacketTunnelProvider DNS interception on iOS 27?
We have a NEPacketTunnelProvider extension that intercepts and modifies DNS responses for specific hostnames as part of its normal operation. On iOS 27, we are seeing this interception being intermittently bypassed. Our extension still receives the DNS query, builds a response, and returns it promptly, but the client occasionally proceeds using a different address, presumably the actual DNS resolution result. This behavior does not reproduce on iOS 26 or earlier. The timing in our logs appears to correlate with the new Connectivity Assist feature (Settings → Wi-Fi), which Apple describes as using cellular data alongside Wi-Fi to improve reliability. Our suspicion is that Connectivity Assist may be performing DNS resolution over a cellular path in parallel, outside the tunnel, causing that resolution path to bypass our provider entirely. We have ruled out response timing and response format issues on our side. Varying the speed and format of our responses does not affect the outcome, suggesting that the behavior is occurring at a layer above the tunnel provider. We have the following questions: Does Connectivity Assist perform DNS resolution on a network path that can bypass an active NEPacketTunnelProvider? Is there any API, entitlement, or supported mechanism to disable Connectivity Assist for an app, or to ensure that all DNS resolution is routed through the active tunnel, similar to previous Wi-Fi Assist opt-out capabilities? Would a NEDNSProxyProvider-based DNS proxy be affected in the same way, or does it operate at a layer that Connectivity Assist cannot bypass? Any guidance, references to relevant documentation, WWDC session content, or confirmation of the expected behavior would be greatly appreciated.
4
0
177
6d
NEURL Filter configuration approved under wrong Developer Team — resubmission blocked by duplicate domain
Hi, My NEURL Filter configuration (ID 9f3cbff8-63de-4c69-bf68-c19cd1c5d842) was approved on August 5, 2026, but it turned out to be attached to the wrong Apple Developer Team — an old individual account (S5VDH23BBZ) I no longer have access to, instead of my actual organization team, KRKJ76BC7W (SCOTTO), which owns and signs the app (bundle ID com.dropbet.DropBet). I contacted Developer Support, who said they couldn't transfer it and suggested replying to the original approval email — I did (on 08/08, then several times) with no response. I then tried resubmitting the same configuration under the correct team (KRKJ76BC7W), but the portal rejected it with: "A configuration with the same PIR Server Domain already exists." So a fresh submission is technically impossible while the original stays attached to the wrong account — the only real fix is transferring or re-attaching the existing approved configuration to KRKJ76BC7W. Has anyone dealt with this kind of Team ID mix-up before, or know who on the Network Extension / NEURL Filter team could help reassign an approved configuration? Happy to provide any additional details. Thanks in advance.
6
0
1.1k
6d
NEURLFilter & Live Caller ID Lookup approved, but PIR token issuer directory returns 404 on device — filter never activates (serverSetupIncomplete)
Our NEURLFilter (URL Filter) and Live Caller ID Lookup entitlements are both Approved, but on device the URL filter never activates — the NEURLFilter session loops starting → stopped, so no URLs are filtered. App: bundle app.scamzero (App ID 6768433709) URL Filter Config ID: 224e33c9-b03b-44ce-87cd-2153c86908ce Live Caller ID Lookup Config ID: 0abdc42b-c98b-4753-8af4-9334d42abdb8 Device: iPhone on iOS 26. Test account has the paid plan that enables the feature. The system log shows the failure is fetching the Privacy Pass token issuer directory through Apple's relay (gateway.icloud.com), not our backend: ciphermld : Failed to fetch Token Issuer Directory. URL: https://gateway.icloud.com/pat-issuer-directory?issuer=issuer.scamzero.app Status Code: 404 → NEURLFilterPlugin setStatus:error — NEAgentURLFilterErrorDomain Code=3 → NESMURLFilterSession status changed to stopping with error 3 → prefilterCleanup (removes the prefilter mmap) → session restarts → loop This looks like NEURLFilterManager.Error.serverSetupIncomplete (9). What we have already verified on our side (everything conforms): We run Apple's unmodified reference PIRService (from apple/pir-service-example). Our issuer directory at https://issuer.scamzero.app/.well-known/private-token-issuer-directory returns 200 with valid token-keys (token-type 2) and issuer-request-uri. OHTTP gateway (gateway.scamzero.app/ohttp-configs and /gateway), PIR service (service.scamzero.app), use case app.scamzero.url.filtering, bearer token AAAA, DNS TXT apple-url-filter=app.scamzero, and the validation URL apple.com/url-filter-test = 1 are all configured. Our pre-launch checks pass. The precomputed Bloom prefilter loads on device with the correct bit/hash/seed and ~37k domains. Key observation: our OHTTP gateway logs show zero successful OHTTP requests from Apple's infrastructure since Aug 10, and gateway.icloud.com/pat-issuer-directory?issuer=issuer.scamzero.app returns 404 on device. This suggests our Privacy Pass issuer is not registered/validated in Apple's PAT issuer directory, even though both entitlements are Approved. Question: How can we get our Privacy Pass issuer (issuer.scamzero.app) registered/validated in Apple's token issuer directory for the use cases app.scamzero.url.filtering and Live Caller ID Lookup? The entitlements are approved, but the on-device token issuer directory lookup fails with 404, which prevents the filter from ever activating. Happy to file a Feedback with a sysdiagnose if useful.
1
0
159
1w
nesessionmanager infinite retry loop causes permanent, unfixable Local Network Access denial (System Settings UI misrepresents actual enforcement state)
Summary On macOS 26.6.2 (25G83) (Tahoe), Chrome fails to load pages hosted on private/local IP addresses (e.g. http://192.168.0.43/, a home AV receiver's web setup page) with ERR_ADDRESS_UNREACHABLE, while Safari loads the identical URL without issue on the same Mac, same network. ping to the target IP succeeds normally. System Settings > Privacy & Security > Local Network shows "Allow" for Chrome — but this is not what's actually being enforced. Traced the root cause to nesessionmanager stuck in an infinite retry loop when attempting to install/update NetworkExtension path-rule policies. Because the daemon never completes a successful policy install, nehelper (the actual enforcement point) continues serving a stale cached "denied" decision indefinitely, regardless of what the Settings UI shows or how many times the toggle is flipped. Key evidence Chrome netlog (chrome://net-export/) shows a genuine TCP connect attempt (not an early permission rejection): text TCP_CONNECT_ATTEMPT --> address = "192.168.0.43:80" -TCP_CONNECT_ATTEMPT --> os_error = 65 -TCP_CONNECT --> net_error = -109 (ERR_ADDRESS_UNREACHABLE) os_error = 65 is BSD EHOSTUNREACH, returned by the kernel at the connect() syscall. Unified log (log stream --predicate 'subsystem == "com.apple.networkextension"') shows the actual enforcement decision: text nehelper: UUID cache hit for com.google.Chrome nehelper: Local network denied by preference for Google Chrome (com.google.Chrome) This reproduced identically across multiple attempts, minutes apart, and survived sudo pkill -f nehelper — confirming the decision is persisted, not just an in-memory cache. While toggling the Local Network switch in System Settings, expecting a pathRules dump, the log instead showed: text NESMPathControllerSession[...]: No UUIDs in the cache for PathRuleDefaultNonSystemIdentifier, populating the cache from the path rules NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 1 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 2 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 3 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 4 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 5 The retry counter kept incrementing with no observed successful completion, meaning any change made via the System Settings toggle can never actually propagate to nehelper. Also confirmed Local Network privacy is not TCC-backed at all: text $ sudo tccutil reset LocalNetwork tccutil: Failed to reset LocalNetwork $ sudo tccutil reset LocalNetwork com.google.Chrome tccutil: Failed to reset LocalNetwork approval status for com.google.Chrome Both fail outright rather than erroring on bad usage, confirming this permission lives entirely in the NetworkExtension path-rule system, with no supported reset command. What did not fix it Toggling the Settings UI switch off/on sudo tccutil reset SystemPolicyNetworkVolumes (wrong TCC service — doesn't apply here) sudo tccutil reset LocalNetwork (fails, see above) Full clean reinstall of Chrome, including all Application Support/Caches/Preferences Safe Mode boot sudo pkill -f nehelper Full normal system restart Launching Chrome with --no-sandbox (rules out Chromium's own internal sandbox as a factor — this is a system-level enforcement, not a Chromium-side block) Working fix Requires temporarily disabling SIP: text In macOS Recovery (csrutil only works from Recovery, not Safe Mode): csrutil disable Back in normal macOS: sudo rm /Library/Preferences/com.apple.networkextension.plist Back in Recovery: csrutil enable Restart normally. nesessionmanager rebuilds the NetworkExtension policy state from scratch on next boot. System Settings > Privacy & Security > Local Network shows a clean list afterward (also clears the separate, previously-known bug where every Chrome auto-update was creating a new duplicate entry in that list rather than updating the existing one). Chrome's next navigation attempt to a local IP correctly triggers a fresh permission prompt, and functions normally once granted. Why this matters Apple's own TN3179 states there's no supported way to reset an app's Local Network privilege to "undetermined." This bug compounds that: once nesessionmanager gets stuck in this retry loop, there is no path back to a working state short of disabling SIP and manually deleting a system preference file. The Settings UI also actively misrepresents the true enforcement state (shows "Allow" while nehelper enforces "denied") with no indication to the user that anything is wrong. This is likely not Chrome-specific — any app relying on Local Network access (smart-home apps, casting/streaming apps, IoT config tools) would hit the identical wall once a Mac's nesessionmanager enters this state. Also filed via Feedback Assistant. Happy to share the full decoded netlog trace if useful — didn't attach it here to keep this post scannable, but can paste the additional detail on request.
1
0
142
1w
SecKeychainGetStatus returns varying errors after SecKeychainOpen succeeds on macOS 26.6.2
Hi, so far i've been using the following API Call Sequence in order to check the keychain status prior to using one of the certificates inside the keychain for signing or verifying remote tls connection. // Step 1: Open keychain SecKeychainRef keychain = nil; OSStatus status = SecKeychainOpen(keychainPath, &keychain); // Result: errSecSuccess (0) — always succeeds, returns valid ref // Step 2: Get keychain status SecKeychainStatus keychainStatus = 0; status = SecKeychainGetStatus(keychain, &keychainStatus); // Result: FAILS : see table below I observed SecKeychainGetStatus return errSecInternalError or errSecInvalidHandle │ Process │ Keychain │ Error │ Code │ │ GUI App │ login.keychain. | errSecInternalError │ -26276 │ │ GUI App │ login.keychain | errSecInvalidHandle │ -25308 │ │ GUI App │ /Library/Keychains/System.keychain | errSecInvalidHandle │ -25308 │ │ Daemon │ /Library/Keychains/System.keychain │ errSecInvalidHandle │ -25308 │ SecKeychainOpen always returns errSecSuccess with a non-null SecKeychainRef SecKeychainGetStatus fails immediately when called on that reference The error for login.keychain varies between calls (not deterministic) The error for System.keychain is consistently errSecInvalidHandle Issue is 100% reproducible. every keychain access attempt fails Started immediately after macOS 26.6.2 upgrade Do you know if there were any changes in 26.6.2 that could have caused this behavior? If this is expected, how should I address it? Thanks
1
0
108
1w
IncludeAllNetworks with excludeRoutes do not seem to work together
When I set IncludeAllNetworks in my VPN profile along with the excludeRoutes for traffic I want to go outside of the tunnel, I see the exclude routes being added to the the route table but when we try and to connect on those hosts something is setting a route to the TUN interface and ignoring the excluded routes in the settings. I'm assuming that some policy before any route table is being looked at is doing this but does that then mean IncludeAllNetworks=true and excludeRoutes are actually incompatible? Is this documented and I just missed it?
2
0
104
1w
NEURLFilterControlProvider: stop(reason:) always receives .none, and is skipped in some teardown paths
Testing NEURLFilterControlProvider on iOS 26.6, I've measured two behaviours around stop(reason:) that I can't reconcile with the documentation, and I'd like to know whether either is intended. Setup: development-signed build, filter configured and running, instrumented stop(reason:) that logs the reason and then emits elapsed-time markers. All timings below are from device log archives collected with "log collect --device". The reason parameter is always .none nesessionmanager records a specific stop reason for itself, then passes .none (0) to the provider. Two different user actions, two different internal reasons, same .none delivered. Disabling via NEURLFilterManager (isEnabled = false, saveToPreferences): 15:20:12.173 nesessionmanager: Setting last stop reason to 31 15:20:12.175 MyProvider: stop() entered, reason=0 (none) 15:20:14.208 MyProvider: stop() completed after 2032ms 15:20:14.209 nesessionmanager: status changed to disconnected, last stop reason Configuration was disabled Disabling the filter from iOS Settings: 15:25:04.167 nesessionmanager: Setting last stop reason to 1 15:25:04.169 MyProvider: stop() entered, reason=0 (none) 15:25:06.198 MyProvider: stop() completed after 2028ms 15:25:06.200 nesessionmanager: status changed to disconnected, last stop reason Stop command received The system clearly knows why the filter stopped, and the values it uses (31, 1) are outside the NEProviderStopReason range, so there appears to be a richer internal enum that isn't mapped through to the provider. Is .none what a URL filter control provider should expect in all cases? If so, is there any supported way for the provider to distinguish a deliberate user disable from system settings, say, an in-app disable, app update or a configuration reload? Is stop(reason:) guaranteed to be called? I do cleanup work in stop(), so I want to know whether it is a reliable lifecycle callback or a best-effort one. I found at least one teardown path where it is skipped entirely. When the configuration is removed as part of the app being deleted, the session is torn down without the provider ever being invoked. The extension is terminated roughly 355 ms before the configuration removal: 15:33:59.361 launchd: service inactive: MyProvider, leaving extension group 15:33:59.363 runningboardd: Executing termination request 15:33:59.431 launchd: remove all extension instances ... total of 0 extension instances were found to remove 15:33:59.716 nesessionmanager: configuration has been removed, stopping 15:33:59.716 nesessionmanager: Setting last stop reason to 32 15:33:59.717 nesessionmanager: plugin NEURLFilterPlugin(...) disposed 15:33:59.717 nesessionmanager: status changed to disconnected, last stop reason Configuration was removed No stop() call appears anywhere in the archive, and the plugin is disposed rather than stopped. The whole teardown takes one millisecond. The ordering looks like the cause: by the time nesessionmanager removes the configuration, the extension process is already gone, so there is nothing left to call stop() on. Thread 713975 describes stopTunnel being called with .providerDisabled in the equivalent situation for a packet tunnel provider, so the behaviour appears to differ by provider type. So my question is about the contract rather than this specific path: should a URL filter control provider treat stop(reason:) as guaranteed, or as best-effort? If it is best-effort, are there other teardown paths where it is skipped, so I can make sure nothing important depends on it running?
1
0
358
1w
Can Multiple Transparent Proxies Work Simultaneously on macOS?
Hi everyone, I’m looking for some clarification regarding "Transparent Proxy configurations on macOS". We have a requirement where multiple security/networking solutions may need to use a "Transparent Proxy" on the same macOS device. For example, one solution may be used for web security/SSL inspection while another solution is required for a different security or network traffic use case. My questions are: 1.Does macOS support configuring and running multiple Transparent Proxy providers simultaneously on the same device? 2. If multiple Transparent Proxy configurations are deployed through MDM, how does macOS determine which proxy handles the traffic? 3.Is there any limitation on the number of Transparent Proxy configurations/providers that can be configured? 4.Can multiple Transparent Proxy providers coexist if they handle different traffic, applications, or network interfaces? 5. If multiple providers are configured, is there a supported mechanism to define priority, ordering, or traffic routing between them? 6. Are there any differences or limitations across recent macOS versions, particularly macOS 15 and macOS 26? 7. Is there any Apple documentation or recommended architecture for deploying multiple Transparent Proxy providers on the same Mac? If anyone has implemented or tested a setup with multiple Transparent Proxy providers on macOS, I’d appreciate any insights, documentation, or real-world experience. Thanks! Vijay
1
0
197
1w
NetworkExtension URL Filter stops during startup only in TestFlight (NEAgentURLFilterErrorDomain Code=3, NEMembershipCheckerErrorDomain Code=3)
Hi Apple Developer Support / community, I am seeing a URL filter startup failure only in TestFlight builds. The same code path works in development and Ad Hoc builds. App setup: Host app bundle id: com.expleo.protectus.filter.main URL filter control extension bundle id: com.expleo.protectus.filter.main.buf Extension point: com.apple.networkextension.url-filter-control Using NEURLFilterManager / NEURLFilterControlProvider on iOS 26.x App Group and network extension entitlements are configured on host + extension Observed behavior: Extension process starts successfully (pid created) Status changes to starting Then updatePrefilterWithCompletionHandler runs Immediately after, plugin transitions to stopping with error 3 System retries in a loop Key logs: Error Domain=NEAgentURLFilterErrorDomain Code=3 Failed to startFilter NEPIRChecker start block reports: Error Domain=com.apple.CipherML Code=1100 Underlying Error Domain=com.apple.CipherML Code=1800 Message says details are redacted Example sequence: NEURLFilterPlugin ... started with pid ... status changed to starting updatePrefilterWithCompletionHandler enter acceptAgentClients enter setStatus:error ... NEAgentURLFilterErrorDomain Code=3 status changed to stopping with error 3 extension disposed / teardown repeats Important detail: This failure is reproducible in TestFlight only. Same code and configuration works in development and Ad Hoc builds. What we already checked: Extension launches and is discovered correctly by neagent App/extension bundle identifiers are correct App group and network extension entitlements are present in source and archive checks We tested startup gating around local bloom/prefilter readiness We still get NEMembershipCheckerErrorDomain Code=3 with CipherML 1100/1800 in TestFlight Questions: Is NEMembershipCheckerErrorDomain Code=3 in this startup path known to indicate PIR membership/status validation failure in distribution context? Are there TestFlight-specific prerequisites or server-side requirements for PIR/CipherML path that differ from development/Ad Hoc? Is there any supported way to get non-redacted diagnostic details for CipherML 1100/1800 (beyond sysdiagnose submission)? Are there recommended fail-open/fail-closed startup patterns when PIR status is temporarily unavailable? If needed, I can provide: Full sysdiagnose timestamped bundle Exact iOS version and device model Repro steps from clean install Full log stream around NEPIRChecker and NEURLFilterPlugin transitions Thanks in advance.
15
0
1.3k
2w
Best practices for handling nw_connection_state_waiting in Transparent Proxy
I am working on a Network Extension (Transparent Proxy) which is used by a DLP to prevent data leaks over the network. For each incoming NEAppProxyTCPFlow, the extension instantiates a custom wrapper object that creates an outbound nw_connection_t to the target host and port using Network framework. When a client application connects to unreachable or blocked ports (e.g., 5222), our extension proxies this connection. As the destination is unreachable, the newly created connection transitions into nw_connection_state_waiting in Network Extension. Since macOS keeps nw_connection_t in the nw_connection_state_waiting state indefinitely, such connections cause system resource leaks. Over time, this leads to: exhaustion of system file descriptors and sockets. system-wide network unavailability until the extension process is killed. Could you provide best-practice recommendations for handling nw_connection_state_waiting in a Network Extension to prevent such resource leaks?
6
0
407
2w
Using URL Filter + Packet Tunnel Provider for blocked URL statistics and App Store approval
Hi Apple Developer Team, We have an iOS app using Network Extension URL Filtering for content filtering. The filtering works, but we need to maintain statistics such as: Number of blocked/allowed URLs Blocked domains/URLs Browser-specific statistics We don't currently see an API from the URL Filter that reliably provides the blocked URL event/details, we already say the latest introduction of reportEndPoint https://developer.apple.com/documentation/networkextension/neurlfiltermanager/reportendpoint?changes=_3,_3, we do not need it for supervised devices and that is mainly designed for supervised devices. We are considering using NEPacketTunnelProvider alongside the URL Filter to collect network-level statistics while keeping the URL Filter responsible for the actual blocking. Our concern is that Could you please clarify: Is it supported to use URL/Content Filter + NEPacketTunnelProvider together for this purpose? Would using Packet Tunnel to collect statistics/network telemetry be considered a misuse of NEPacketTunnelProvider, since it is primarily intended for VPN functionality? Does adding the packet-tunnel-provider Network Extension entitlement require separate Apple approval? Could this architecture create an App Store review/distribution issue? Is there an Apple-supported API for obtaining the URL/domain and block decision directly from the URL/Content Filter, which would be preferable to monitoring packets? Our goal is not to replace the content filter with Packet Tunnel. We want to keep filtering in the appropriate Network Extension and use the appropriate mechanism to maintain browser-specific blocked/allowed statistics. Thank you.
1
0
177
2w
Prevent multiple DNS Proxy Filter when switching users
Hello Team, We have a System Extension with Provider Type "DNS Proxy". We have embedded the System Extension in GUI target which registered as LaunchAgent. We found NEDNSProxyManager saves the proxy configuration in the caller's preferences. Due to that we see a prompt for Network Extension when switching users. On allowing that we see multiple DNS filter in the System Settings->Network->Filters even though one DNS Filter can enabled which is annoying. Question 1: Is this expected for non MDM users? Are the users expected to authorise Network extension when switching users. Question 2: Is there a way to prevent the multiple DNS filter for both MDM and non MDM users? To prevent multiple filters, we identified a solution to embed the System Extension in our LaunchDaemon target. So the proxy configuration will be save in the root preference. But with this approach we ended up with an error [OSSystemExtensionErrorDomain error 13] during OSSystemExtensionRequest.deactivationRequest. Question 3: Is there a way to avoid OSSystemExtensionErrorDomain 13 when deactivating System extension from our LaunchDaemon process? Question 4: What is the best practice in terms of embedding and deploying DNS Proxy System Extension for managed and non managed environment. Also if user expected to see multiple DNS filter. I suggest to show the filter that saved for that user's preference. Thank you.
1
0
709
4w
Network Extension Resources
General: Forums subtopic: App & System Services > Networking DevForums tag: Network Extension Network Extension framework documentation Routing your VPN network traffic article Filtering Network Traffic sample code TN3120 Expected use cases for Network Extension packet tunnel providers technote TN3134 Network Extension provider deployment technote TN3165 Packet Filter is not API technote Network Extension and VPN Glossary forums post Debugging a Network Extension Provider forums post Exporting a Developer ID Network Extension forums post Network Extension Framework Entitlements forums post Network Extension vs ad hoc techniques on macOS forums post Network Extension Provider Packaging forums post NWEndpoint History and Advice forums post Extra-ordinary Networking forums post URL filter: WWDC 2025 Session 234 Filter and tunnel network traffic with NetworkExtension URL filters documentation Filtering traffic by URL sample code Setting up a PIR server for URL filtering sample code Using the Bloom filter tool to configure a URL filter sample code PIR Service Example open source server sample and specifically its documentation Wi-Fi management: Understanding NEHotspotConfigurationErrorInternal forums post See also Networking Resources for general networking resources, including information about Wi-Fi. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
Replies
0
Boosts
0
Views
4.0k
Activity
Aug ’26
Best practices for parallelizing handleNewFlow(_:) or async verdicts in NETransparentProxyProvider
Hello, I am working on network traffic analyser for DLP. The solution is based on NETransparentProxyProvider due to necessity data modifying. To my knowledge, the Transparent Proxy API does not have a concept of an "asynchronous/delayed verdict" for flow authorization. The handleNewFlow(_:) method appears to be invoked sequentially for each new network flow that matches our interception rules. The challenge we are facing is that a portion of our proxy rules and configurations resides in a LaunchDaemon, which the Network Extension communicates with via XPC. Such separation is required by our cross-platform code base. Thus, we are forced to make a synchronous XPC call within handleNewFlow(_:) and block the thread while waiting for the interception verdict from the daemon. This creates a significant bottleneck when establishing new network flows. The current implementation works without issues, but we are always looking for ways to improve performance. Are there any best practices or recommended approaches for parallelizing handleNewFlow(_:)? Thank you in advance.
Replies
1
Boosts
0
Views
52
Activity
18h
IKEv2 Personal VPN: Child SA torn down after 120s idle (NEIKEv2ErrorDomain Code=15) with DisconnectOnIdle already NO
We ship a consumer VPN app on iOS, iPadOS and tvOS using a Personal VPN configuration: NEVPNManager with NEVPNProtocolIKEv2, EAP-MSCHAPv2, no MDM profile installed. After exactly 120 seconds with no traffic, iOS destroys the Child SA and disconnects the tunnel. I would like to know whether that timer is configurable, and if not, what the intended mitigation is. From a device sysdiagnose (iPhone, iOS 26.6, build 23G71): NEIPSecDBStatsUpdate: SA is idle for past 120 secs KernelSASession[1, IKEv2 Session Database] idle timeout SA Internal SAID = 2 SPI = C53320D1 Direction = Outbound ChildSA[1] state Connected -> Disconnected error Domain=NEIKEv2ErrorDomain Code=15 "IdleTimeout" <NEIKEv2Provider: Primary Tunnel>: stopping tunnel since Child disconnected nesessionmanager: plugin disconnected with reason "Tunnel was idle for too long" This happened 12 times across a 9.2 hour overnight capture on one idle device. Median time before the tunnel re-established was 14m35s. DISCONNECT-ON-IDLE IS NOT ENABLED The same sysdiagnose shows the plugin's own configuration as: disconnectOnIdle = NO disconnectOnIdleTimeout = 0 These are the stock defaults. There is no disconnectOnIdle property on the public NEVPNProtocol, so a Personal VPN app cannot set them either way. The installed SA parameters contain no idle field at all, only "Lifetime Seconds = 1800", which is honoured correctly: the same capture shows 20 clean rekey cycles. WHAT I HAVE RULED OUT iOS logs a distinct stop reason for each of the following, and none of them occurred across 24 teardowns: "On Demand Disconnect rule matched", "Tunnel was terminated by the server", "Server is not responding", "Network changed, tunnel no longer viable", "Device went to sleep", "Stop command received". The only reason recorded was "Tunnel was idle for too long". On the gateway (strongSwan), IKE rekey and reauth are disabled, uniqueids is never, and DPD is answered in roughly 200ms right up to the teardown. The gateway considers the tunnel healthy at the moment iOS tears it down. DPD does not reset the timer, which makes sense: DPD is an INFORMATIONAL exchange on the IKE SA, whereas the log shows the timer measuring the OUTBOUND Child SA (SAID 2). THE APP CANNOT SEE THIS HAPPEN NEVPNConnection.fetchLastDisconnectError() returns nil for this teardown, because the error is in NEIKEv2ErrorDomain rather than NEVPNConnectionErrorDomain. The app has no supported way to detect that the tunnel dropped for this reason, or to distinguish it from a user-initiated disconnect. It is only visible in a sysdiagnose. TRAFFIC IS NOT HELD DURING THE RECONNECT With Connect On Demand enabled (NEOnDemandRuleConnect, interfaceTypeMatch .any), traffic after the teardown does not wait for the tunnel. On device wake following one of these drops: 01:32:43 device wakes 01:32:43 [C331 ... :443] path:satisfied @0.001s, interface: en0[802.11], uses wifi 01:32:44 [C331 ... :443] flow:finish_connect @0.623s (over en0) 01:32:47 tunnel status changed to connected so flows complete over the physical interface for several seconds before the VPN re-establishes. On-demand triggered the reconnect but did not delay the traffic. For a VPN product this window is the part that concerns me most. QUESTIONS Is the 120 second Child SA idle timeout configurable for a Personal VPN using NEVPNProtocolIKEv2, from the app or from a configuration profile? If DisconnectOnIdle / DisconnectOnIdleTimer are meant to control it, why does the teardown occur when they are NO / 0? If it is not configurable, is application-generated keepalive traffic the intended workaround? If so, how is that expected to work while iOS has the app suspended, which is exactly when a tunnel goes idle? Would server originated traffic that elicits a client response be a supported approach? Is there any supported way for an app to be notified of this teardown, given that fetchLastDisconnectError() returns nil for it? Is the behaviour in "traffic is not held during the reconnect" expected for NEOnDemandRuleConnect, or should matching flows be delayed until the tunnel is up? Is includeAllNetworks the only supported way to close that window? Happy to supply the full sysdiagnose privately.
Replies
1
Boosts
0
Views
230
Activity
1d
Memory leak in CFNetwork (PACClient/PACQuery) when using NETransparentProxyProvider with Auto Proxy Discovery enabled
Hello, I have encountered unexpected behavior when running a Network Extension that implements NETransparentProxyProvider. This extension is part of a DLP (Data Loss Prevention) solution. If the "Auto proxy discovery" option is enabled for the Wi-Fi connection on the managed host, the leaks tool reports memory leaks with the following root cycles: ... 11 (1.03K) ROOT CYCLE: <CFRunLoopSource 0xa430cc540> [192] 10 (864 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACClient> 0xa430f4000> [224] CYCLE BACK TO <CFRunLoopSource 0xa430cc540> [192] 6 (400 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACQuery> 0xa43118080> [128] 2 (80 bytes) ROOT CYCLE: 0xa42ca9000 [32] 1 (48 bytes) ROOT CYCLE: <__NSMallocBlock__ 0xa42804de0> [48] CFNetwork invocation function for block in PAC::PACClient::initialize(void const*, __CFURL cons..." 1 (32 bytes) ROOT CYCLE: <std::__shared_ptr_pointer<BlockHolderVar<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>*, SmartBlockWithArgs<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>::Deleter> 0xa4343da80> [32] 2 (160 bytes) <NSURL 0xa42840310> [112] 1 (48 bytes) _clients --> <CFString 0xa42c08fc0> [48] 1 (160 bytes) <NWConcrete_nw_pac_resolver 0xa4280cb40> [160] 1 (48 bytes) <CFError 0xa42804ed0> [48] 1 (32 bytes) <std::__shared_ptr_pointer<__CFError*, Deleter_CFRelease> 0xa4343dc20> [32] ... 11 (1.03K) ROOT CYCLE: <CFRunLoopSource 0xa43128540> [192] 10 (864 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACClient> 0xa430f4a80> [224] CYCLE BACK TO <CFRunLoopSource 0xa43128540> [192] 6 (400 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACQuery> 0xa43118880> [128] 2 (80 bytes) ROOT CYCLE: 0xa428105e0 [32] 1 (48 bytes) ROOT CYCLE: <__NSMallocBlock__ 0xa428887b0> [48] CFNetwork invocation function for block in PAC::PACClient::initialize(void const*, __CFURL cons..." 1 (32 bytes) ROOT CYCLE: <std::__shared_ptr_pointer<BlockHolderVar<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>*, SmartBlockWithArgs<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>::Deleter> 0xa4343f5c0> [32] 2 (160 bytes) <NSURL 0xa428424c0> [112] 1 (48 bytes) _clients --> <CFString 0xa42c0a640> [48] 1 (160 bytes) <NWConcrete_nw_pac_resolver 0xa4280d7c0> [160] 1 (48 bytes) <CFError 0xa42888390> [48] 1 (32 bytes) <std::__shared_ptr_pointer<__CFError*, Deleter_CFRelease> 0xa4343f4c0> [32] ... The extension creates an nw_connection_t to the remote host for each handled flow like this: nw_parameters_t parameters = nw_parameters_create_secure_tcp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION); nw_endpoint_t connectTo = nw_endpoint_create_host([endpoint.hostname UTF8String], [endpoint.port UTF8String]); nw_connection_t connection = nw_connection_create(connectTo, parameters); When "Auto proxy discovery" is disabled, everything works as expected, and no memory leaks or issues are observed. Could you please advise on how to resolve or work around this issue? Thank you in advance!
Replies
2
Boosts
0
Views
319
Activity
4d
Potential Network Extension memory leak
I've been investigating memory consumption issues with a network extension. It seems as though the "leak" exists within the network extension framework. Looking at a memory graph, there appears to be a _socketFlows dictionary or similar in the network extension framework that holds all of the flows and they continue to pile up and consume memory. The problem seems to be particularly bad when I leave my MacBook plugged in and running over the weekend and come back on Monday. For instance, in that time period, the memory consumption grew from <100MB to >1GB. To try to narrow this down to see if it was an "us" problem or truly in the framework, I installed Objective-See Foundation's Lulu tool, which also uses a network extension. It similarly saw memory grow from around 30MB or less to 600MB in the same time frame. Has anyone else seen this? We do have an open feedback ticket FB18731867 that might be related that has to do with seeing multiple instances of the same data filter provider started and maybe running in a network extension.
Replies
5
Boosts
0
Views
679
Activity
5d
NEPacketTunnelProvider for local device wide bandwidth pacing without a remote VPN server
We are developing a consumer digital wellbeing app and are evaluating an optional feature that deliberately slows network traffic when explicitly enabled by the user. The proposed architecture is: NEPacketTunnelProvider device wide traffic no MDM or per-app VPN no remote VPN server packet processing/forwarding performed locally on device no application layer inspection no traffic profiling or advertising use no network data uploaded to our servers bounded latency/bandwidth shaping only user can disable it immediately The goal is user controlled “intentional bandwidth” rather than security, privacy filtering, circumvention, or remote VPN access. I’d like to clarify: Is NEPacketTunnelProvider an appropriate public API for this kind of local device wide bandwidth pacing? Is it supported to consume packets via packetFlow, process/forward them locally, and send them to the public Internet without a remote VPN endpoint? Is intentional bandwidth/latency shaping considered an acceptable use of the Network Extension entitlement? Would this still be treated as a “VPN service” for App Review purposes even though no traffic is routed through a remote VPN service? If so, would the organization-enrollment requirement for VPN apps apply? Is there another public API Apple recommends for this use case? I understand that the App Review questions may ultimately need to be answered by App Review rather than the forums. My main goal here is to validate whether this is a supported NetworkExtension architecture before investing in the packet processing implementation.
Replies
1
Boosts
0
Views
89
Activity
5d
Does "Connectivity Assist" bypass NEPacketTunnelProvider DNS interception on iOS 27?
We have a NEPacketTunnelProvider extension that intercepts and modifies DNS responses for specific hostnames as part of its normal operation. On iOS 27, we are seeing this interception being intermittently bypassed. Our extension still receives the DNS query, builds a response, and returns it promptly, but the client occasionally proceeds using a different address, presumably the actual DNS resolution result. This behavior does not reproduce on iOS 26 or earlier. The timing in our logs appears to correlate with the new Connectivity Assist feature (Settings → Wi-Fi), which Apple describes as using cellular data alongside Wi-Fi to improve reliability. Our suspicion is that Connectivity Assist may be performing DNS resolution over a cellular path in parallel, outside the tunnel, causing that resolution path to bypass our provider entirely. We have ruled out response timing and response format issues on our side. Varying the speed and format of our responses does not affect the outcome, suggesting that the behavior is occurring at a layer above the tunnel provider. We have the following questions: Does Connectivity Assist perform DNS resolution on a network path that can bypass an active NEPacketTunnelProvider? Is there any API, entitlement, or supported mechanism to disable Connectivity Assist for an app, or to ensure that all DNS resolution is routed through the active tunnel, similar to previous Wi-Fi Assist opt-out capabilities? Would a NEDNSProxyProvider-based DNS proxy be affected in the same way, or does it operate at a layer that Connectivity Assist cannot bypass? Any guidance, references to relevant documentation, WWDC session content, or confirmation of the expected behavior would be greatly appreciated.
Replies
4
Boosts
0
Views
177
Activity
6d
NEURL Filter configuration approved under wrong Developer Team — resubmission blocked by duplicate domain
Hi, My NEURL Filter configuration (ID 9f3cbff8-63de-4c69-bf68-c19cd1c5d842) was approved on August 5, 2026, but it turned out to be attached to the wrong Apple Developer Team — an old individual account (S5VDH23BBZ) I no longer have access to, instead of my actual organization team, KRKJ76BC7W (SCOTTO), which owns and signs the app (bundle ID com.dropbet.DropBet). I contacted Developer Support, who said they couldn't transfer it and suggested replying to the original approval email — I did (on 08/08, then several times) with no response. I then tried resubmitting the same configuration under the correct team (KRKJ76BC7W), but the portal rejected it with: "A configuration with the same PIR Server Domain already exists." So a fresh submission is technically impossible while the original stays attached to the wrong account — the only real fix is transferring or re-attaching the existing approved configuration to KRKJ76BC7W. Has anyone dealt with this kind of Team ID mix-up before, or know who on the Network Extension / NEURL Filter team could help reassign an approved configuration? Happy to provide any additional details. Thanks in advance.
Replies
6
Boosts
0
Views
1.1k
Activity
6d
NEURLFilter & Live Caller ID Lookup approved, but PIR token issuer directory returns 404 on device — filter never activates (serverSetupIncomplete)
Our NEURLFilter (URL Filter) and Live Caller ID Lookup entitlements are both Approved, but on device the URL filter never activates — the NEURLFilter session loops starting → stopped, so no URLs are filtered. App: bundle app.scamzero (App ID 6768433709) URL Filter Config ID: 224e33c9-b03b-44ce-87cd-2153c86908ce Live Caller ID Lookup Config ID: 0abdc42b-c98b-4753-8af4-9334d42abdb8 Device: iPhone on iOS 26. Test account has the paid plan that enables the feature. The system log shows the failure is fetching the Privacy Pass token issuer directory through Apple's relay (gateway.icloud.com), not our backend: ciphermld : Failed to fetch Token Issuer Directory. URL: https://gateway.icloud.com/pat-issuer-directory?issuer=issuer.scamzero.app Status Code: 404 → NEURLFilterPlugin setStatus:error — NEAgentURLFilterErrorDomain Code=3 → NESMURLFilterSession status changed to stopping with error 3 → prefilterCleanup (removes the prefilter mmap) → session restarts → loop This looks like NEURLFilterManager.Error.serverSetupIncomplete (9). What we have already verified on our side (everything conforms): We run Apple's unmodified reference PIRService (from apple/pir-service-example). Our issuer directory at https://issuer.scamzero.app/.well-known/private-token-issuer-directory returns 200 with valid token-keys (token-type 2) and issuer-request-uri. OHTTP gateway (gateway.scamzero.app/ohttp-configs and /gateway), PIR service (service.scamzero.app), use case app.scamzero.url.filtering, bearer token AAAA, DNS TXT apple-url-filter=app.scamzero, and the validation URL apple.com/url-filter-test = 1 are all configured. Our pre-launch checks pass. The precomputed Bloom prefilter loads on device with the correct bit/hash/seed and ~37k domains. Key observation: our OHTTP gateway logs show zero successful OHTTP requests from Apple's infrastructure since Aug 10, and gateway.icloud.com/pat-issuer-directory?issuer=issuer.scamzero.app returns 404 on device. This suggests our Privacy Pass issuer is not registered/validated in Apple's PAT issuer directory, even though both entitlements are Approved. Question: How can we get our Privacy Pass issuer (issuer.scamzero.app) registered/validated in Apple's token issuer directory for the use cases app.scamzero.url.filtering and Live Caller ID Lookup? The entitlements are approved, but the on-device token issuer directory lookup fails with 404, which prevents the filter from ever activating. Happy to file a Feedback with a sysdiagnose if useful.
Replies
1
Boosts
0
Views
159
Activity
1w
nesessionmanager infinite retry loop causes permanent, unfixable Local Network Access denial (System Settings UI misrepresents actual enforcement state)
Summary On macOS 26.6.2 (25G83) (Tahoe), Chrome fails to load pages hosted on private/local IP addresses (e.g. http://192.168.0.43/, a home AV receiver's web setup page) with ERR_ADDRESS_UNREACHABLE, while Safari loads the identical URL without issue on the same Mac, same network. ping to the target IP succeeds normally. System Settings > Privacy & Security > Local Network shows "Allow" for Chrome — but this is not what's actually being enforced. Traced the root cause to nesessionmanager stuck in an infinite retry loop when attempting to install/update NetworkExtension path-rule policies. Because the daemon never completes a successful policy install, nehelper (the actual enforcement point) continues serving a stale cached "denied" decision indefinitely, regardless of what the Settings UI shows or how many times the toggle is flipped. Key evidence Chrome netlog (chrome://net-export/) shows a genuine TCP connect attempt (not an early permission rejection): text TCP_CONNECT_ATTEMPT --> address = "192.168.0.43:80" -TCP_CONNECT_ATTEMPT --> os_error = 65 -TCP_CONNECT --> net_error = -109 (ERR_ADDRESS_UNREACHABLE) os_error = 65 is BSD EHOSTUNREACH, returned by the kernel at the connect() syscall. Unified log (log stream --predicate 'subsystem == "com.apple.networkextension"') shows the actual enforcement decision: text nehelper: UUID cache hit for com.google.Chrome nehelper: Local network denied by preference for Google Chrome (com.google.Chrome) This reproduced identically across multiple attempts, minutes apart, and survived sudo pkill -f nehelper — confirming the decision is persisted, not just an in-memory cache. While toggling the Local Network switch in System Settings, expecting a pathRules dump, the log instead showed: text NESMPathControllerSession[...]: No UUIDs in the cache for PathRuleDefaultNonSystemIdentifier, populating the cache from the path rules NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 1 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 2 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 3 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 4 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 5 The retry counter kept incrementing with no observed successful completion, meaning any change made via the System Settings toggle can never actually propagate to nehelper. Also confirmed Local Network privacy is not TCC-backed at all: text $ sudo tccutil reset LocalNetwork tccutil: Failed to reset LocalNetwork $ sudo tccutil reset LocalNetwork com.google.Chrome tccutil: Failed to reset LocalNetwork approval status for com.google.Chrome Both fail outright rather than erroring on bad usage, confirming this permission lives entirely in the NetworkExtension path-rule system, with no supported reset command. What did not fix it Toggling the Settings UI switch off/on sudo tccutil reset SystemPolicyNetworkVolumes (wrong TCC service — doesn't apply here) sudo tccutil reset LocalNetwork (fails, see above) Full clean reinstall of Chrome, including all Application Support/Caches/Preferences Safe Mode boot sudo pkill -f nehelper Full normal system restart Launching Chrome with --no-sandbox (rules out Chromium's own internal sandbox as a factor — this is a system-level enforcement, not a Chromium-side block) Working fix Requires temporarily disabling SIP: text In macOS Recovery (csrutil only works from Recovery, not Safe Mode): csrutil disable Back in normal macOS: sudo rm /Library/Preferences/com.apple.networkextension.plist Back in Recovery: csrutil enable Restart normally. nesessionmanager rebuilds the NetworkExtension policy state from scratch on next boot. System Settings > Privacy & Security > Local Network shows a clean list afterward (also clears the separate, previously-known bug where every Chrome auto-update was creating a new duplicate entry in that list rather than updating the existing one). Chrome's next navigation attempt to a local IP correctly triggers a fresh permission prompt, and functions normally once granted. Why this matters Apple's own TN3179 states there's no supported way to reset an app's Local Network privilege to "undetermined." This bug compounds that: once nesessionmanager gets stuck in this retry loop, there is no path back to a working state short of disabling SIP and manually deleting a system preference file. The Settings UI also actively misrepresents the true enforcement state (shows "Allow" while nehelper enforces "denied") with no indication to the user that anything is wrong. This is likely not Chrome-specific — any app relying on Local Network access (smart-home apps, casting/streaming apps, IoT config tools) would hit the identical wall once a Mac's nesessionmanager enters this state. Also filed via Feedback Assistant. Happy to share the full decoded netlog trace if useful — didn't attach it here to keep this post scannable, but can paste the additional detail on request.
Replies
1
Boosts
0
Views
142
Activity
1w
SecKeychainGetStatus returns varying errors after SecKeychainOpen succeeds on macOS 26.6.2
Hi, so far i've been using the following API Call Sequence in order to check the keychain status prior to using one of the certificates inside the keychain for signing or verifying remote tls connection. // Step 1: Open keychain SecKeychainRef keychain = nil; OSStatus status = SecKeychainOpen(keychainPath, &keychain); // Result: errSecSuccess (0) — always succeeds, returns valid ref // Step 2: Get keychain status SecKeychainStatus keychainStatus = 0; status = SecKeychainGetStatus(keychain, &keychainStatus); // Result: FAILS : see table below I observed SecKeychainGetStatus return errSecInternalError or errSecInvalidHandle │ Process │ Keychain │ Error │ Code │ │ GUI App │ login.keychain. | errSecInternalError │ -26276 │ │ GUI App │ login.keychain | errSecInvalidHandle │ -25308 │ │ GUI App │ /Library/Keychains/System.keychain | errSecInvalidHandle │ -25308 │ │ Daemon │ /Library/Keychains/System.keychain │ errSecInvalidHandle │ -25308 │ SecKeychainOpen always returns errSecSuccess with a non-null SecKeychainRef SecKeychainGetStatus fails immediately when called on that reference The error for login.keychain varies between calls (not deterministic) The error for System.keychain is consistently errSecInvalidHandle Issue is 100% reproducible. every keychain access attempt fails Started immediately after macOS 26.6.2 upgrade Do you know if there were any changes in 26.6.2 that could have caused this behavior? If this is expected, how should I address it? Thanks
Replies
1
Boosts
0
Views
108
Activity
1w
IncludeAllNetworks with excludeRoutes do not seem to work together
When I set IncludeAllNetworks in my VPN profile along with the excludeRoutes for traffic I want to go outside of the tunnel, I see the exclude routes being added to the the route table but when we try and to connect on those hosts something is setting a route to the TUN interface and ignoring the excluded routes in the settings. I'm assuming that some policy before any route table is being looked at is doing this but does that then mean IncludeAllNetworks=true and excludeRoutes are actually incompatible? Is this documented and I just missed it?
Replies
2
Boosts
0
Views
104
Activity
1w
NEURLFilterControlProvider: stop(reason:) always receives .none, and is skipped in some teardown paths
Testing NEURLFilterControlProvider on iOS 26.6, I've measured two behaviours around stop(reason:) that I can't reconcile with the documentation, and I'd like to know whether either is intended. Setup: development-signed build, filter configured and running, instrumented stop(reason:) that logs the reason and then emits elapsed-time markers. All timings below are from device log archives collected with "log collect --device". The reason parameter is always .none nesessionmanager records a specific stop reason for itself, then passes .none (0) to the provider. Two different user actions, two different internal reasons, same .none delivered. Disabling via NEURLFilterManager (isEnabled = false, saveToPreferences): 15:20:12.173 nesessionmanager: Setting last stop reason to 31 15:20:12.175 MyProvider: stop() entered, reason=0 (none) 15:20:14.208 MyProvider: stop() completed after 2032ms 15:20:14.209 nesessionmanager: status changed to disconnected, last stop reason Configuration was disabled Disabling the filter from iOS Settings: 15:25:04.167 nesessionmanager: Setting last stop reason to 1 15:25:04.169 MyProvider: stop() entered, reason=0 (none) 15:25:06.198 MyProvider: stop() completed after 2028ms 15:25:06.200 nesessionmanager: status changed to disconnected, last stop reason Stop command received The system clearly knows why the filter stopped, and the values it uses (31, 1) are outside the NEProviderStopReason range, so there appears to be a richer internal enum that isn't mapped through to the provider. Is .none what a URL filter control provider should expect in all cases? If so, is there any supported way for the provider to distinguish a deliberate user disable from system settings, say, an in-app disable, app update or a configuration reload? Is stop(reason:) guaranteed to be called? I do cleanup work in stop(), so I want to know whether it is a reliable lifecycle callback or a best-effort one. I found at least one teardown path where it is skipped entirely. When the configuration is removed as part of the app being deleted, the session is torn down without the provider ever being invoked. The extension is terminated roughly 355 ms before the configuration removal: 15:33:59.361 launchd: service inactive: MyProvider, leaving extension group 15:33:59.363 runningboardd: Executing termination request 15:33:59.431 launchd: remove all extension instances ... total of 0 extension instances were found to remove 15:33:59.716 nesessionmanager: configuration has been removed, stopping 15:33:59.716 nesessionmanager: Setting last stop reason to 32 15:33:59.717 nesessionmanager: plugin NEURLFilterPlugin(...) disposed 15:33:59.717 nesessionmanager: status changed to disconnected, last stop reason Configuration was removed No stop() call appears anywhere in the archive, and the plugin is disposed rather than stopped. The whole teardown takes one millisecond. The ordering looks like the cause: by the time nesessionmanager removes the configuration, the extension process is already gone, so there is nothing left to call stop() on. Thread 713975 describes stopTunnel being called with .providerDisabled in the equivalent situation for a packet tunnel provider, so the behaviour appears to differ by provider type. So my question is about the contract rather than this specific path: should a URL filter control provider treat stop(reason:) as guaranteed, or as best-effort? If it is best-effort, are there other teardown paths where it is skipped, so I can make sure nothing important depends on it running?
Replies
1
Boosts
0
Views
358
Activity
1w
Can Multiple Transparent Proxies Work Simultaneously on macOS?
Hi everyone, I’m looking for some clarification regarding "Transparent Proxy configurations on macOS". We have a requirement where multiple security/networking solutions may need to use a "Transparent Proxy" on the same macOS device. For example, one solution may be used for web security/SSL inspection while another solution is required for a different security or network traffic use case. My questions are: 1.Does macOS support configuring and running multiple Transparent Proxy providers simultaneously on the same device? 2. If multiple Transparent Proxy configurations are deployed through MDM, how does macOS determine which proxy handles the traffic? 3.Is there any limitation on the number of Transparent Proxy configurations/providers that can be configured? 4.Can multiple Transparent Proxy providers coexist if they handle different traffic, applications, or network interfaces? 5. If multiple providers are configured, is there a supported mechanism to define priority, ordering, or traffic routing between them? 6. Are there any differences or limitations across recent macOS versions, particularly macOS 15 and macOS 26? 7. Is there any Apple documentation or recommended architecture for deploying multiple Transparent Proxy providers on the same Mac? If anyone has implemented or tested a setup with multiple Transparent Proxy providers on macOS, I’d appreciate any insights, documentation, or real-world experience. Thanks! Vijay
Replies
1
Boosts
0
Views
197
Activity
1w
VPN node telemetry data
Hello, I making VPN client app and would like to clarify Apple Guideline 5.4 article: "VPN apps may not sell, use, or disclose to third parties any data for any purpose, and must commit to this in their privacy policy", does it mean anonymous nodes/servers telemetry also can't be provided to third parties? Thank you
Replies
1
Boosts
0
Views
654
Activity
2w
NetworkExtension URL Filter stops during startup only in TestFlight (NEAgentURLFilterErrorDomain Code=3, NEMembershipCheckerErrorDomain Code=3)
Hi Apple Developer Support / community, I am seeing a URL filter startup failure only in TestFlight builds. The same code path works in development and Ad Hoc builds. App setup: Host app bundle id: com.expleo.protectus.filter.main URL filter control extension bundle id: com.expleo.protectus.filter.main.buf Extension point: com.apple.networkextension.url-filter-control Using NEURLFilterManager / NEURLFilterControlProvider on iOS 26.x App Group and network extension entitlements are configured on host + extension Observed behavior: Extension process starts successfully (pid created) Status changes to starting Then updatePrefilterWithCompletionHandler runs Immediately after, plugin transitions to stopping with error 3 System retries in a loop Key logs: Error Domain=NEAgentURLFilterErrorDomain Code=3 Failed to startFilter NEPIRChecker start block reports: Error Domain=com.apple.CipherML Code=1100 Underlying Error Domain=com.apple.CipherML Code=1800 Message says details are redacted Example sequence: NEURLFilterPlugin ... started with pid ... status changed to starting updatePrefilterWithCompletionHandler enter acceptAgentClients enter setStatus:error ... NEAgentURLFilterErrorDomain Code=3 status changed to stopping with error 3 extension disposed / teardown repeats Important detail: This failure is reproducible in TestFlight only. Same code and configuration works in development and Ad Hoc builds. What we already checked: Extension launches and is discovered correctly by neagent App/extension bundle identifiers are correct App group and network extension entitlements are present in source and archive checks We tested startup gating around local bloom/prefilter readiness We still get NEMembershipCheckerErrorDomain Code=3 with CipherML 1100/1800 in TestFlight Questions: Is NEMembershipCheckerErrorDomain Code=3 in this startup path known to indicate PIR membership/status validation failure in distribution context? Are there TestFlight-specific prerequisites or server-side requirements for PIR/CipherML path that differ from development/Ad Hoc? Is there any supported way to get non-redacted diagnostic details for CipherML 1100/1800 (beyond sysdiagnose submission)? Are there recommended fail-open/fail-closed startup patterns when PIR status is temporarily unavailable? If needed, I can provide: Full sysdiagnose timestamped bundle Exact iOS version and device model Repro steps from clean install Full log stream around NEPIRChecker and NEURLFilterPlugin transitions Thanks in advance.
Replies
15
Boosts
0
Views
1.3k
Activity
2w
Best practices for handling nw_connection_state_waiting in Transparent Proxy
I am working on a Network Extension (Transparent Proxy) which is used by a DLP to prevent data leaks over the network. For each incoming NEAppProxyTCPFlow, the extension instantiates a custom wrapper object that creates an outbound nw_connection_t to the target host and port using Network framework. When a client application connects to unreachable or blocked ports (e.g., 5222), our extension proxies this connection. As the destination is unreachable, the newly created connection transitions into nw_connection_state_waiting in Network Extension. Since macOS keeps nw_connection_t in the nw_connection_state_waiting state indefinitely, such connections cause system resource leaks. Over time, this leads to: exhaustion of system file descriptors and sockets. system-wide network unavailability until the extension process is killed. Could you provide best-practice recommendations for handling nw_connection_state_waiting in a Network Extension to prevent such resource leaks?
Replies
6
Boosts
0
Views
407
Activity
2w
Using URL Filter + Packet Tunnel Provider for blocked URL statistics and App Store approval
Hi Apple Developer Team, We have an iOS app using Network Extension URL Filtering for content filtering. The filtering works, but we need to maintain statistics such as: Number of blocked/allowed URLs Blocked domains/URLs Browser-specific statistics We don't currently see an API from the URL Filter that reliably provides the blocked URL event/details, we already say the latest introduction of reportEndPoint https://developer.apple.com/documentation/networkextension/neurlfiltermanager/reportendpoint?changes=_3,_3, we do not need it for supervised devices and that is mainly designed for supervised devices. We are considering using NEPacketTunnelProvider alongside the URL Filter to collect network-level statistics while keeping the URL Filter responsible for the actual blocking. Our concern is that Could you please clarify: Is it supported to use URL/Content Filter + NEPacketTunnelProvider together for this purpose? Would using Packet Tunnel to collect statistics/network telemetry be considered a misuse of NEPacketTunnelProvider, since it is primarily intended for VPN functionality? Does adding the packet-tunnel-provider Network Extension entitlement require separate Apple approval? Could this architecture create an App Store review/distribution issue? Is there an Apple-supported API for obtaining the URL/domain and block decision directly from the URL/Content Filter, which would be preferable to monitoring packets? Our goal is not to replace the content filter with Packet Tunnel. We want to keep filtering in the appropriate Network Extension and use the appropriate mechanism to maintain browser-specific blocked/allowed statistics. Thank you.
Replies
1
Boosts
0
Views
177
Activity
2w
Launch Transparent Proxy Network Extension in machine login window on macOS
Hi, Is there a way on macOS to launch Transparent Proxy Network Extension in machine login window? on-demand rule doesn't help. I have this use case that after reboot machine, I want to launch Transparent Proxy Network Extension in machine login window before user log into machine.
Replies
2
Boosts
0
Views
431
Activity
3w
Prevent multiple DNS Proxy Filter when switching users
Hello Team, We have a System Extension with Provider Type "DNS Proxy". We have embedded the System Extension in GUI target which registered as LaunchAgent. We found NEDNSProxyManager saves the proxy configuration in the caller's preferences. Due to that we see a prompt for Network Extension when switching users. On allowing that we see multiple DNS filter in the System Settings->Network->Filters even though one DNS Filter can enabled which is annoying. Question 1: Is this expected for non MDM users? Are the users expected to authorise Network extension when switching users. Question 2: Is there a way to prevent the multiple DNS filter for both MDM and non MDM users? To prevent multiple filters, we identified a solution to embed the System Extension in our LaunchDaemon target. So the proxy configuration will be save in the root preference. But with this approach we ended up with an error [OSSystemExtensionErrorDomain error 13] during OSSystemExtensionRequest.deactivationRequest. Question 3: Is there a way to avoid OSSystemExtensionErrorDomain 13 when deactivating System extension from our LaunchDaemon process? Question 4: What is the best practice in terms of embedding and deploying DNS Proxy System Extension for managed and non managed environment. Also if user expected to see multiple DNS filter. I suggest to show the filter that saved for that user's preference. Thank you.
Replies
1
Boosts
0
Views
709
Activity
4w